/*
 * Copyright (c) 2020 rxi
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */

#ifdef _cplusplus
extern "C" {
#endif

/*-----------------------------------------------------------------------------
 * HEADER FILES
 *---------------------------------------------------------------------------*/
#include "gmi_log.h"
#include "gmi_status.h"

/*-----------------------------------------------------------------------------
 * MACRO DEFINITIONS
 *---------------------------------------------------------------------------*/
#define MAX_CALLBACKS 32  // 最大回调次数

/*-----------------------------------------------------------------------------
 * DATA STRUCTURES
 *---------------------------------------------------------------------------*/

/**
 * @brief 日志回调函数配置
 */
typedef struct gmi_callback_st
{
    log_outFn fn;  ///< 回调指针函数
    void* udata;   ///< log输出数据
    int level;     // 回调等级
} GMI_LOG_CALLBACK;

/**
 * @brief 日志对象
 */
static struct gmi_log_s
{
    void* udata;
    log_LockFn lock;
    int level;
    bool quiet;
    GMI_LOG_CALLBACK callbacks[MAX_CALLBACKS];
} L;

static const char* level_strings[] =
    {"TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL"};

#ifdef LOG_USE_COLOR
static const char* level_colors[] =
    {"\x1b[94m", "\x1b[36m", "\x1b[32m", "\x1b[33m", "\x1b[31m", "\x1b[35m"};
#endif

/*-----------------------------------------------------------------------------
 * FUNCTIONS DEFINITIONS
 *---------------------------------------------------------------------------*/
static void stdout_callback(GMI_LOG_EVENT* event)
{
    char buf[16];
    buf[strftime(buf, sizeof(buf), "%H:%M:%S", event->time)] = '\0';

#ifdef LOG_USE_COLOR
    fprintf((FILE*)event->udata,
            "%s %s%-5s\x1b[0m \x1b[90m%s:%d:\x1b[0m ",
            buf,
            level_colors[event->level],
            level_strings[event->level],
            event->file,
            event->line);
#else
    fprintf((FILE*)event->udata,
            "%s %-5s %s:%d: ",
            buf,
            level_strings[event->level],
            event->file,
            event->line);
#endif

    vfprintf((FILE*)event->udata, event->format, event->ap);
    fprintf((FILE*)event->udata, "\n");
    fflush((FILE*)event->udata);
}

static void file_callback(GMI_LOG_EVENT* event)
{
    char buf[64];
    buf[strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", event->time)] = '\0';
    fprintf((FILE*)event->udata,
            "%s %-5s %s:%d: ",
            buf,
            level_strings[event->level],
            event->file,
            event->line);
    vfprintf((FILE*)event->udata, event->format, event->ap);
    fprintf((FILE*)event->udata, "\n");
    fflush((FILE*)event->udata);
}

static void lock(void)
{
    if (L.lock)
    {
        L.lock(true, L.udata);
    }
}

static void unlock(void)
{
    if (L.lock)
    {
        L.lock(false, L.udata);
    }
}

const char* gmi_log_level_string(int level)
{
    return level_strings[level];
}

void gmi_log_set_lock(log_LockFn fn, void* udata)
{
    L.lock  = fn;
    L.udata = udata;
}

void gmi_log_set_level(int level)
{
    L.level = level;
}

void gmi_log_set_quiet(bool enable)
{
    L.quiet = enable;
}

int gmi_log_add_callback(log_outFn fn, void* udata, int level)
{
    for (int i = 0; i < MAX_CALLBACKS; i++)
    {
        if (!L.callbacks[i].fn)
        {
            L.callbacks[i] = (GMI_LOG_CALLBACK){fn, udata, level};
            return GMI_STATUS_OK;
        }
    }
    return GMI_STATUS_RECURSION_OUT_OF_TIMES;
}

int gmi_log_add_fp(FILE* fp, int level)
{
    return gmi_log_add_callback(file_callback, fp, level);
}

static void gmi_init_event(GMI_LOG_EVENT* event, void* udata)
{
    if (!event->time)
    {
        time_t t    = time(NULL);
        event->time = localtime(&t);
    }
    event->udata = udata;
}

void gmi_log_out(int level, const char* file, int line, const char* format, ...)
{
    GMI_LOG_EVENT event = {
        .format = format,
        .file   = file,
        .line   = line,
        .level  = level,
    };

    lock();

    if (!L.quiet && level >= L.level)
    {
        gmi_init_event(&event, stderr);
        va_start(event.ap, format);
        stdout_callback(&event);
        va_end(event.ap);
    }

    for (int i = 0; i < MAX_CALLBACKS && L.callbacks[i].fn; i++)
    {
        GMI_LOG_CALLBACK* cb = &L.callbacks[i];
        if (level >= cb->level)
        {
            gmi_init_event(&event, cb->udata);
            va_start(event.ap, format);
            cb->fn(&event);
            va_end(event.ap);
        }
    }

    unlock();
}

#ifdef _cplusplus
}
#endif